<?php

namespace Reusify\Dto;

use ArrayAccess;
use Countable;
use IteratorAggregate;
use Traversable;
use ArrayIterator;
use ReflectionClass;
use ReflectionProperty;

abstract class BaseDto implements
    \JsonSerializable,
    ArrayAccess,
    IteratorAggregate,
    Countable
{

    /**
     * @param array|IteratorAggregate $data
     * @param array $map
     */
    public function __construct($data = [],array $map = [])
    {
        $this->fill($data,$map);
    }

    public function __toString()
    {
        return $this->toJson();
    }

    public function toJson()
    {
        return json_encode($this->jsonSerialize(),JSON_UNESCAPED_UNICODE);
    }


    /** 批量赋值
     * @param array|IteratorAggregate $data
     * @param array $map ['当前dto 的属性名'=>'$data 的 key']
     * @return $this
     */
    public function fill($data,$map = []): self
    {
        foreach ($data as $key => $value)
        {
            if (!property_exists($this, $key))
            {
                continue;
            }
            $this->setPropertyValue($key, $value);
        }
        foreach ($map as $key => $k)
        {
            if (!isset($data[$k]))
            {
                throw new \LogicException('$data 没有'.$k);
            }
            $v = $data[$k];
            $this->setPropertyValue($key, $v);
        }
        return $this;
    }

    /**
     * 设置对象属性值（支持私有/保护）
     */
    protected function setPropertyValue(string $name,$value): void
    {
        $reflection = new ReflectionClass($this);
        if (!$reflection->hasProperty($name)) {
            throw new \InvalidArgumentException("属性不存在: $name");
        }
        $property = $reflection->getProperty($name);
        $property->setAccessible(true);
        $property->setValue($this, $value);
    }
    /**
     * 将对象转为数组（包含私有/保护属性，递归处理子对象）
     */
    public function toArray(): array
    {
        return $this->castToArray($this);
    }

    public function jsonSerialize()
    {
        return $this->toArray();
    }

    public function getIterator(): Traversable
    {
        return new ArrayIterator($this->toArray());
    }

    public function offsetExists($offset): bool
    {
        return array_key_exists($offset, $this->toArray());
    }

    public function offsetGet($offset)
    {
        $array = $this->toArray();
        return $array[$offset] ?? null;
    }

    public function offsetSet($offset, $value): void
    {
        // 默认只读，如你需要可写，我可以改为设置属性
        throw new \LogicException("BaseObject 是只读的，无法设置属性：$offset");
    }

    public function offsetUnset($offset): void
    {
        throw new \LogicException("BaseObject 是只读的，无法取消属性：$offset");
    }

    public function count(): int
    {
        return count($this->toArray());
    }

    /**
     * 递归转为数组
     */
    protected function castToArray(object $object): array
    {
        $result = [];
        $reflection = new ReflectionClass($object);

        foreach ($reflection->getProperties() as $property) {
            $property->setAccessible(true);
            $name = $property->getName();
            //没赋值就跳过！！！！
            if ($property->isInitialized($object)) {
                $value = $property->getValue($object);
            } else {
                continue;
            }

            if (is_object($value)) {
                if ($value instanceof self) {
                    $result[$name] = $this->castToArray($value);
                } else {
                    $result[$name] = (array) $value;
                }
            } elseif (is_array($value)) {
                $result[$name] = $this->castArrayItems($value);
            } else {
                $result[$name] = $value;
            }
        }

        return $result;
    }

    /**
     * 递归处理数组中的对象项
     */
    protected function castArrayItems(array $array): array
    {
        $result = [];

        foreach ($array as $key => $value) {
            if (is_object($value)) {
                if ($value instanceof self) {
                    $result[$key] = $this->castToArray($value);
                } else {
                    $result[$key] = (array) $value;
                }
            } elseif (is_array($value)) {
                $result[$key] = $this->castArrayItems($value);
            } else {
                $result[$key] = $value;
            }
        }

        return $result;
    }
}
